﻿using Minio;
using Minio.DataModel;
using Minio.Exceptions;
using System.IO;

namespace LS.Screening.UI.Common
{
    internal class MinioService
    {
        private readonly IMinioClient _client;
        public Action<int> MutilPartProcessAction { get; set; }

        /// <summary>
        /// 构造函数
        /// </summary>
        public MinioService()
        {
            var client = new MinioClient()
                .WithEndpoint($"{MinioConfig.Instance.Host}:{MinioConfig.Instance.Port}")
                .WithRegion("us-east-1")
                .WithCredentials(MinioConfig.Instance.AccessKey, MinioConfig.Instance.SecretKey);
            if (MinioConfig.Instance.IsEnableHttps)
            {
                client = client.WithSSL();
            }
            _client = client.Build();
        }

        /// <summary>
        /// 检查存储桶是否存在。
        /// </summary>
        /// <param name="bucketName">存储桶名称。</param>
        /// <returns></returns>
        public async Task<bool> CheckBucketExistAsync(string bucketName)
        {
            if (string.IsNullOrEmpty(bucketName))
            {
                throw new ArgumentNullException(nameof(bucketName));
            }
            var args = new BucketExistsArgs().WithBucket(bucketName);
            return await _client.BucketExistsAsync(args).ConfigureAwait(false);
        }

        /// <summary>
        /// 创建一个存储桶,如果存在则不创建
        /// </summary>
        /// <param name="bucketName">存储桶名称。</param>
        /// <param name="location">可选参数。默认是us-east-1。</param>
        /// <returns></returns>
        public async Task<bool> CreateBucketAsync(string bucketName)
        {
            bool flag = await CheckBucketExistAsync(bucketName);
            if (flag)
            {
                throw new Exception($"Bucket '{bucketName}' already exists.");
            }
            else
            {
                await _client.MakeBucketAsync(new MakeBucketArgs().WithBucket(bucketName)).ConfigureAwait(false);
                return true;
            }
        }

        /// <summary>
        /// 检测文件是否存在
        /// </summary>
        /// <param name="bucketName"></param>
        /// <param name="objectName"></param>
        /// <returns></returns>
        public async Task<bool> ObjectsExistsAsync(string bucketName, string objectName)
        {
            if (string.IsNullOrEmpty(bucketName))
            {
                throw new ArgumentNullException(nameof(bucketName));
            }
            objectName = FormatObjectName(objectName);
            try
            {
                var result = await GetObjectMetadataAsync(bucketName, objectName).ConfigureAwait(false);
                return result != null;
            }
            catch (ObjectNotFoundException)
            {
                return false;
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 获取对象的元数据
        /// </summary>
        /// <param name="bucketName"></param>
        /// <param name="objectName"></param>
        /// <param name="versionID"></param>
        /// <param name="matchEtag"></param>
        /// <param name="modifiedSince"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public async Task<ItemMeta> GetObjectMetadataAsync(string bucketName, string objectName, string versionID = null, string matchEtag = null, DateTime? modifiedSince = null)
        {
            if (string.IsNullOrEmpty(bucketName))
            {
                throw new ArgumentNullException(nameof(bucketName));
            }
            objectName = FormatObjectName(objectName);
            StatObjectArgs args = new StatObjectArgs()
                .WithBucket(bucketName)
                .WithObject(objectName)
                .WithVersionId(versionID)
                .WithMatchETag(matchEtag);
            if (modifiedSince.HasValue)
            {
                args = args.WithModifiedSince(modifiedSince.Value);
            }
            ObjectStat statObject = await _client.StatObjectAsync(args).ConfigureAwait(false);

            return new ItemMeta()
            {
                ObjectName = statObject.ObjectName,
                Size = statObject.Size,
                LastModified = statObject.LastModified,
                ETag = statObject.ETag,
                ContentType = statObject.ContentType,
                IsEnableHttps = MinioConfig.Instance.IsEnableHttps,
                MetaData = statObject.MetaData
            };
        }

        /// <summary>
        /// 返回对象数据的流。
        /// </summary>
        /// <param name="bucketName">存储桶名称。</param>
        /// <param name="objectName">存储桶里的对象名称。</param>
        /// <param name="callback">处理流的回调函数。</param>
        /// <param name="cancellationToken">可选参数。默认是default(CancellationToken)</param>
        /// <returns></returns>
        public async Task GetObjectAsync(string bucketName, string objectName, Action<Stream> callback = null, CancellationToken cancellationToken = default)
        {
            if (string.IsNullOrEmpty(bucketName))
            {
                throw new ArgumentNullException(nameof(bucketName));
            }
            if (!await CheckBucketExistAsync(bucketName))
            {
                await CreateBucketAsync(bucketName).ConfigureAwait(false);
            }
            if (!await ObjectsExistsAsync(bucketName, objectName))
            {
                throw new Exception("要获取的文件不存在");
            }
            objectName = FormatObjectName(objectName);
            var args = new GetObjectArgs()
                .WithBucket(bucketName)
                .WithObject(objectName)
                .WithCallbackStream((stream) =>
                {
                    callback?.Invoke(stream);
                });
            _ = await _client.GetObjectAsync(args, cancellationToken).ConfigureAwait(false);
        }

        /// <summary>
        /// 通过Stream上传对象。
        /// </summary>
        /// <param name="bucketName">存储桶名称。</param>
        /// <param name="objectName">存储桶里的对象名称。</param>
        /// <param name="fs">要上传的Stream对象。</param>
        /// <param name="cancellationToken">可选参数。默认是default(CancellationToken)</param>
        /// <returns></returns>
        public async Task<bool> PutObjectAsync(string bucketName, string objectName, Stream fs, CancellationToken cancellationToken = default)
        {
            if (string.IsNullOrEmpty(bucketName))
            {
                throw new ArgumentNullException(nameof(bucketName));
            }
            if (!await CheckBucketExistAsync(bucketName))
            {
                await CreateBucketAsync(bucketName);
            }
            objectName = FormatObjectName(objectName);
            string fileName = objectName;
            if (fs is FileStream fileStream)
            {
                fileName = fileStream.Name;
            }
            var contentType = fileName.GetContentType();
            var args = new PutObjectArgs()
                .WithBucket(bucketName)
                .WithObject(objectName)
                .WithStreamData(fs)
                .WithObjectSize(fs.Length)
                .WithContentType(contentType);
            await _client.PutObjectAsync(args, cancellationToken).ConfigureAwait(false);
            return true;
        }

        /// <summary>
        /// 从objectName指定的对象中将数据拷贝到destObjectName指定的对象。
        /// </summary>
        /// <param name="bucketName">源存储桶名称。</param>
        /// <param name="objectName">源存储桶中的源对象名称。</param>
        /// <param name="destBucketName">目标存储桶名称。</param>
        /// <param name="destObjectName">要创建的目标对象名称,如果为空，默认为源对象名称。</param>
        /// <returns></returns>
        public async Task<bool> CopyObjectAsync(string bucketName, string objectName, string destBucketName, string destObjectName = null)
        {
            if (string.IsNullOrEmpty(bucketName))
            {
                throw new ArgumentNullException(nameof(bucketName));
            }
            objectName = FormatObjectName(objectName);
            if (string.IsNullOrEmpty(destBucketName))
            {
                destBucketName = bucketName;
            }
            destObjectName = FormatObjectName(destObjectName);
            CopySourceObjectArgs cpSrcArgs = new CopySourceObjectArgs()
                .WithBucket(bucketName)
                .WithObject(objectName);
            CopyObjectArgs args = new CopyObjectArgs()
                .WithBucket(destBucketName)
                .WithObject(destObjectName)
                .WithCopyObjectSource(cpSrcArgs);
            await _client.CopyObjectAsync(args).ConfigureAwait(false);
            return true;
        }

        /// <summary>
        /// 删除一个对象。
        /// </summary>
        /// <param name="bucketName">存储桶名称。</param>
        /// <param name="objectName">存储桶里的对象名称。</param>
        /// <returns></returns>
        public async Task<bool> RemoveObjectAsync(string bucketName, string objectName)
        {
            if (!await CheckBucketExistAsync(bucketName))
            {
                await CreateBucketAsync(bucketName);
            }
            if (string.IsNullOrEmpty(bucketName))
            {
                throw new ArgumentNullException(nameof(bucketName));
            }
            objectName = FormatObjectName(objectName);
            RemoveObjectArgs args = new RemoveObjectArgs()
                .WithBucket(bucketName)
                .WithObject(objectName);
            await _client.RemoveObjectAsync(args).ConfigureAwait(false);
            return true;
        }

        /// <summary>
        /// 格式化对象名称
        /// </summary>
        /// <param name="objectName"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        private string FormatObjectName(string objectName)
        {
            if (string.IsNullOrEmpty(objectName) || objectName == "/")
            {
                throw new ArgumentNullException(nameof(objectName));
            }
            if (objectName.Contains("\\"))
            {
                objectName = objectName.Replace("\\", "/");
            }
            if (objectName.StartsWith('/'))
            {
                return objectName.TrimStart('/');
            }
            return objectName;
        }
    }

    internal static class FileExtension
    {
        public static string GetContentType(this string file)
        {
            string fileName = Path.GetFileName(file);
            var fileExtioins = Path.GetExtension(fileName);
            var contentType = "application/octet-stream";
            return contentType;
        }
    }
}
